package com.zonerobotics.zBotAPI.PollEngine;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.zonerobotics.zBotAPI.Enums.EzEvent;
import com.zonerobotics.zBotAPI.Listeners.IBotListener;
import com.zonerobotics.zBotAPI.ZBot.ZBot;
import com.zonerobotics.zBotAPI.ZBot.ZBotStats;



public class ZPollService extends Thread{
	   

	    // our streams
	    OutputStream stmOutput = null;
	    InputStream stmInput = null;
	    
	    ZBot bot = null;
	    boolean running = false;
	    boolean killThread = false;
	    boolean connected = true;
		private int maxErrors;
		private int pktRetries = 5;
		private String TAG = "ZPollService";
		public ZBotStats zbs = new ZBotStats();
		public double[] timeStamps = new double[5];
		public double[] biggestTimeStamps = new double[5];
		
		private IBotListener logger = null;
		
	   
	    /**
	     * constructor takes the input and output stream to start polling the
	     * bot. it will not start polling until you start the service
	     * @param stmInput
	     * @param stmOutput
	     */
	    public ZPollService( InputStream stmInput, OutputStream stmOutput, ZBot bot) {
			this.stmOutput = stmOutput;
			this.stmInput = stmInput;
			this.bot = bot;
		}

	    
	    /**
	     * this will cause the service to start polling, this could also
	     * be a restart, it simply sets the main thread running again.
	     * this will also start the thread
	     */
	    public void startSvc()
	    {
	    	this.killThread = false;
	    	if(!this.isAlive())
	    	{
	    		this.start();
	    	}
	    	this.running = true;
	    }
	    
	    /**
	     * this will cause the polling service to stop polling the connection
	     * remains open and ready to be resumed
	     */
	    public void stopSvc()
	    {
	    	this.running = false;
	    	this.killThread = true;
	    }
	    
	    /**
	     * this should kill all threads and close all streams associated with this
	     * service
	     */
	    public void killSvc()
	    {
	    	this.running = true;
	    	this.connected = false;
	    }

		public boolean isConnected() {
			return connected;
		}

		// we need these sorts of things to be abstract, we want all plain java here ! 
		private void sendLogMsg(String msg)
	    {
			if(logger == null)
			{
				System.out.println(msg);
			}else{
				logger.onLogMessage(msg);
			}
	    }

		/***
		 * this sets the log listener all of the messages will be handed up there if this is null
		 * we will push them all out to the stdout
		 * @param ll
		 */
		public void setLogListener(IBotListener ll)
		{
			this.logger = ll;
		}
	 
	    public void run() {

    		try {
	    	while(!running)
	    	{
	    		if(killThread)
	    		{
	    			return;
	    		}
				Thread.sleep(500);
	    	}
    		if(killThread)
    		{
    			return;
    		}
	    	
	    	// packets for use
	    	final ZPacket sendPkt = new ZPacket();
	    	final ZPacket respPkt = new ZPacket();
	    	boolean printNew = false;

	        while(connected)
	        {
	        	// last timer, from rx done to here
	        	timeStamps[4] = System.nanoTime() - timeStamps[4];
	        	// check old to new
	        	for(int i=0; i<5; i++)
	        	{
	        		if(biggestTimeStamps[i] < timeStamps[i])
	        		{
	        			biggestTimeStamps[i] = timeStamps[i];
	        			printNew = true;
	        		}
	        	}
	        	if(printNew)
	        	{
	        		System.out.println("New Times: " 
	        				+ biggestTimeStamps[0]/1000000 + " : " 
	        				+ biggestTimeStamps[1]/1000000 + " : " 
	        				+ biggestTimeStamps[2]/1000000  + " : " 
	        				+ biggestTimeStamps[3]/1000000  + " : " 
	        				+ biggestTimeStamps[4]/1000000  + " : " 
	        				);
	        		printNew = false;
	        	}

		    	// set timestamps up 
		    	timeStamps[0] = System.nanoTime();
		    	
		    	while(!running)
		    	{
		    		Thread.sleep(500);
		    	}
	        	try{
	        		pktRetries = 5; // reset TODO make this configurable ? 
	        		
	        		// get the next packet to send out
	        		timeStamps[1] = System.nanoTime();
	        		bot.getNextPacket(sendPkt);
	        		timeStamps[1] = System.nanoTime() - timeStamps[1];
	        		// send out and get response
	        		txrxPacket(sendPkt, respPkt);
	        		
	        		while(respPkt == null && pktRetries > 0)
	        		{
	        			txrxPacket(sendPkt, respPkt);
	        			pktRetries--;
	        		}
	        	}catch(Exception e)
	        	{
	        		//if(D) Log.e(TAG, "fail in main loop: " + e.getMessage());
	        		sendLogMsg(TAG + ":fail in main loop txrx: " + e.getMessage());
	        		connected = false;
	        	}
	        		
	        	try{
	
	        		// check response 
	        		if(respPkt == null || !respPkt.isValid())
	        		{
	        			sendLogMsg(TAG + ":Retries Left:" + maxErrors);
	        			if(maxErrors == 0)
	        			{
		        			connected = false;
		        			break; 
	        			}else{
	        				maxErrors--;
	        			}
	        		}else{
	        			// update the bot given the latest packet
	        			// if we cant there is a huge issue!
	        			if(!bot.updateFromPacket(respPkt))
	        			{
	        				connected = false;
	        			}
	       
	        		}

	        	}catch(Exception e)
	        	{
	        		//if(D) Log.e(TAG, "fail in main loop: " + e.getMessage());
	        		sendLogMsg(TAG + ":fail in main loop, proc: " + e.getMessage());
	        		connected = false;
	        	}
	        	
	        	
	        }// end while connected
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				sendLogMsg(TAG + ":horrible fail: " + e.getMessage());
				shutDown();
			}
    		try {
				this.stmInput.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
    		try {
				this.stmOutput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    		bot.raiseEvent(EzEvent.disconnected);
	        sendLogMsg(TAG + ":disconnected from zBot, try again");
	        shutDown();
	    }
	    
	    private void shutDown()
	    {
	    	try {
				this.stmInput.close();
				this.stmOutput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	
	    }
        
//        private boolean txPacket(ZPacket txPkt)
//	    {
//	    	// write the tx pkt to the stream
//	    	try {
//				stmOutput.write(txPkt.toBytes());
//			} catch (IOException e) {
//				// TODO Auto-generated catch block
//				sendLogMsg(TAG + ":failure writing packet " + e.getMessage());
//				return false;
//			}
//	    	return true;
//	    }
	    static byte[] response = new byte[4096*4];
	    private void txrxPacket(ZPacket txPkt, ZPacket rxPkt)
	    {
	    	// expected response length is the requested len + header size + (crc, no crc in resp) 
	    	int expectedRespLen = txPkt.getReadLength() + txPkt.getHeader().length;
	    	// buffer to hold the data we got back 
	    	
	    	// where are we 
	    	int offset = 0;
	    	// max timeout
	    	int timeoutMS = 1500; // makes 5 sec timeout
	    	timeStamps[0] = System.nanoTime() - timeStamps[0]; // from start to rx
	    	timeStamps[2] = System.nanoTime();
	    	// write the tx pkt to the stream
	    	try {
				stmOutput.write(txPkt.toBytes());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				sendLogMsg(TAG + ":failure writing packet " + e.getMessage());
				return;
			}
	    	timeStamps[2] = System.nanoTime() - timeStamps[2];
	    	zbs.addNextTXBps(txPkt.fullSizeInBytes, timeStamps[2]);
	    	timeStamps[3] = System.nanoTime();
	    	long timeoutAt = System.currentTimeMillis() + timeoutMS;
	    	try {
	    		while(offset < expectedRespLen)
	    		{
	    			if(stmInput.available() > 0)
	    			{
	    				offset += stmInput.read(response, offset, response.length-offset);
	    			}else{
	    				if(timeoutAt > System.currentTimeMillis())
	    				{

	    				}else{
		    				sendLogMsg(TAG + ":zbot not responding");
		    				return;
	    				}
	    			}
	    		}
	    		timeStamps[3] = System.nanoTime() - timeStamps[3];
	    		zbs.addNextRXBps(offset, timeStamps[3]);
	    		timeStamps[4] = System.nanoTime();

				// get it into a easy to use packet
				rxPkt.fromBytes(response);


			} catch (IOException e) {
				sendLogMsg(TAG + ":failure parsing packet" + e.getMessage());
				return;
			}
	    }
	 

}
